package com.example.mq.mqclient;

import com.example.mq.common.*;

import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Connection {


    private Socket socket=null;

    //需要管理多个 channel 使用一个哈希表 把若干个  channel 组织起来
    private ConcurrentHashMap<String,Channel> channelMap=new ConcurrentHashMap<>();

    private ExecutorService callbackPoll= null;

    private InputStream inputStream;
    private OutputStream outputStream;
    private DataInputStream dataInputStream;
    private DataOutputStream dataOutputStream;


    public Connection(String host,int port) throws IOException {
            socket=new Socket(host,port);
            inputStream=socket.getInputStream();
            outputStream= socket.getOutputStream();
            dataInputStream=new DataInputStream(inputStream);
            dataOutputStream=new DataOutputStream(outputStream);
            callbackPoll= Executors.newFixedThreadPool(4);
            //创建一个扫描线程 这个线程负责不停的从 socket 中读取响应数据
            //再把这个数据交给 Chanel 处理
            Thread t=new Thread(()->{
                try {
                    while (!socket.isClosed()){
                        Response response = readResponse();
                        dispatchResponse(response);
                    }
                } catch (SocketException e) {
                    //连接正常端断开 之间忽略
                    System.out.println("[Connection] 连接正常断开!");
                } catch (IOException |ClassNotFoundException | MqException e) {
                    e.printStackTrace();
                    System.out.println("[Connection] 连接异常断开!");
                }
            });
            t.start();
    }
    public void close()  {
        //关闭Connection(相当于TCP连接) 释放上述资源
        try {
            callbackPoll.shutdownNow();
            channelMap.clear();
            inputStream.close();
            outputStream.close();
            socket.close();
        }catch (IOException e){
            e.printStackTrace();
        }

    }

    //使用这个方法 来分别处理 当前响应是一个针对控制请求的响应 还是服务器推送的消息
    private void dispatchResponse(Response response) throws IOException, ClassNotFoundException, MqException {
        //服务器推送来的消息数据
        if(response.getType()==0xc){
            SubScribeReturns subScribeReturns= (SubScribeReturns) BinaryTool.fromBytes(response.getPayload());
            //根据 channelId 找到对应的 channel 对象 执行该对象内部的回调
            Channel channel = channelMap.get(subScribeReturns.getChannelId());
            if(channel==null){
                throw new MqException("[Connection] 该消息对应的 channel 在客户端中不存在 channelId="+channel.getChannelId());
            }
            //使用线程池执行回调方法
            callbackPoll.submit(()->{
                try {
                    channel.getConsumer().handleDelivery(subScribeReturns.getConsumerTag(),subScribeReturns.getBasicProperties(),subScribeReturns.getBody());
                } catch (MqException | IOException e) {
                    e.printStackTrace();
                }
            });
        }else {
            //当前响应是刚才请求的控制请求的响应
            BasicReturns basicReturns = (BasicReturns) BinaryTool.fromBytes(response.getPayload());
            //把结果放到 Channel 对应的哈希表中
            Channel channel=channelMap.get(basicReturns.getChannelId());
            if(channel==null){
                throw new MqException("[Connection] 该消息对应的 channel 在客户端中不存在 channelId="+channel.getChannelId());
            }
            channel.putReturns(basicReturns);
        }
    }


    //发送请求
    public void writeRequest(Request request) throws IOException {
        //根据约定好的格式 发送请求
        dataOutputStream.writeInt(request.getType());
        dataOutputStream.writeInt(request.getLength());
        dataOutputStream.write(request.getPayload());
        dataOutputStream.flush();
        try {
            System.out.println("[Connection] 发送请求! type="+request.getType()+", length="+request.getType()+", Payload="+BinaryTool.fromBytes(request.getPayload()));
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    //读取响应
    public Response readResponse() throws IOException {
        Response response=new Response();
        response.setType(dataInputStream.readInt());
        response.setLength(dataInputStream.readInt());
        byte[] payload= new  byte[response.getLength()];
        int n=dataInputStream.read(payload);
        if(n!=response.getLength()){
            throw new IOException("读取的响应数据不完整");
        }
        response.setPayload(payload);
        try {
            System.out.println("[Connection] 收到响应! type="+response.getType()+", length="+response.getType()+", payload= "+BinaryTool.fromBytes(response.getPayload()));
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return response;
    }

    //通过这个方法,在 Connection 能够创建出一个 Channel
    public Channel createChannel() throws IOException {
        String channelId= "C-"+UUID.randomUUID().toString();
        Channel channel=new Channel(channelId,this);
        //把这个 channel 放到管理 channel 的哈希表中
        channelMap.put(channelId,channel);
        //同时也需要把 "创建 channel " 的这个消息 告诉服务器
        boolean ok=channel.createChannel();
        if(!ok){
            //服务器这里创建失败了 !! 整个这次创建的操作都失败了
            //把之前加入的 channel 删除
            channelMap.remove(channelId);
            return null;
        }
        return channel;
    }


}
